Skip to content

Fix infinite recursion on circular selectable dependencies#469

Open
alex-tan wants to merge 1 commit into
mmkal:mainfrom
alex-tan:atan/migra-circular-dependency-fix
Open

Fix infinite recursion on circular selectable dependencies#469
alex-tan wants to merge 1 commit into
mmkal:mainfrom
alex-tan:atan/migra-circular-dependency-fix

Conversation

@alex-tan

@alex-tan alex-tan commented Jul 1, 2026

Copy link
Copy Markdown

get_related_for_item recursed forever when selectables had circular dependent_on/dependents chains (e.g. mutually-referencing views), causing "RangeError: Maximum call stack size exceeded" during load_deps_all. Add a visited set to guard against revisiting the same signature, and filter the root signature out of the result everywhere it reappears (not just the first occurrence via slice(1), which missed later reappearances introduced by the cycle).

Includes a regression test that fails with the old recursion (confirmed via RangeError) and passes with this fix.

get_related_for_item recursed forever when selectables had circular
dependent_on/dependents chains (e.g. mutually-referencing views),
causing "RangeError: Maximum call stack size exceeded" during
load_deps_all. Add a visited set to guard against revisiting the same
signature, and filter the root signature out of the result everywhere
it reappears (not just the first occurrence via slice(1), which missed
later reappearances introduced by the cycle).

Includes a regression test that fails with the old recursion (confirmed
via RangeError) and passes with this fix.
@vercel

vercel Bot commented Jul 1, 2026

Copy link
Copy Markdown

Someone is attempting to deploy a commit to the mmkal Team on Vercel.

A member of the Team first needs to authorize it.

mmkal added a commit to iterate/sqlfu that referenced this pull request Jul 8, 2026
## Summary

Fixes the vendored PostgreSQL schemainspect traversal for circular
selectable dependencies, matching the issue fixed upstream in
`mmkal/pgkit#469`. `PostgreSQL.load_deps_all()` now stops revisiting
signatures during recursive expansion and removes the root selectable
wherever it reappears in circular walks.

The red repro is intentionally preserved as commit `9d21f30` before the
fix commit, so CI/history can show the original failure.

## Before / After

Before:

```ts
await database.load_deps_all();
// RangeError: Maximum call stack size exceeded
```

After:

```ts
await database.load_deps_all();
first.dependent_on_all; // [second.signature]
second.dependent_on_all; // [first.signature]
```

## Verification

- Red repro commit: `9d21f30` failed locally with `RangeError: Maximum
call stack size exceeded`.
- Green fix: `pnpm --dir packages/pg exec vitest run
test/circular-selectable-deps.test.ts`
- Typecheck: `pnpm --filter @sqlfu/pg typecheck`
- Note: `pnpm --filter @sqlfu/pg test --
circular-selectable-deps.test.ts` also loads DB-backed pg suites in this
repo and fails locally without the test Postgres container.

<!-- package-size:start -->
<details>
<summary>Package size — packed 280.5 kB (no change)</summary>

## Package size

|  | main | this PR | Δ |
| - | - | - | - |
| packed | 280.5 kB | 280.5 kB | 0 |
| unpacked | 1.12 MB | 1.12 MB | 0 |
| files | 226 | 226 | 0 |

### `dist/vendor/*.js` bundles

|  | main | this PR | Δ |
| - | - | - | - |
| `vendor/sha256.js` | 4.3 kB | 4.3 kB | 0 |
| `vendor/sql-formatter/*.js` | 94.5 kB | 94.5 kB | 0 |
| `vendor/sqlfu-sqlite-parser/*.js` | 17.2 kB | 17.2 kB | 0 |
| `vendor/standard-schema/*.js` | 2.8 kB | 2.8 kB | 0 |
| `vendor/typesql/*.js` | 134.8 kB | 134.8 kB | 0 |

_Measured with `npm pack --dry-run --json` on `sqlfu` (0.0.3-7 on main
vs 0.0.3-7 on this PR)._

</details>
<!-- package-size:end -->

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Low Risk**
> Small, targeted fix in schema-inspection dependency expansion with a
focused regression test; no auth, security, or data-path changes.
> 
> **Overview**
> Fixes **stack overflow** when vendored PostgreSQL schemainspect
expands transitive selectable dependencies that form a cycle (aligned
with upstream `mmkal/pgkit#469`).
> 
> `PostgreSQL.load_deps_all()` now tracks a **visited set** during
recursive `get_related_for_item` walks so signatures are not re-entered,
and **strips the root selectable’s signature** from `dependent_on_all` /
`dependents_all` when it would appear again in a circular path. A
**regression test** builds two mutually dependent views and asserts
stable transitive lists instead of a `RangeError`.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
a9dddad. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant